package self;

import com.google.gson.Gson;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Tuple;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by 刘万祥 on 2017/5/5 0005.
 */
public class Chapter02 {

    private final String z_recent_key = "recent:";
    private final String h_login_key = "login:";
    private final String z_delay_key = "delay:";
    private final String z_schedule_key = "schedule:";
    private final String z_viewed_key_prefix = "viewed:";
    private final String z_cart_key_prefix = "cart:";
    private final String z_cache_key_prefix = "cache:";
    private final String s_inv_key_prefix = "inv:";


    public static void main(String[] args) {
        Jedis conn = new Jedis("localhost");
        conn.select(15);

        Chapter02 chapter02 = new Chapter02();
//        chapter02.testCheckCookies(conn);
//        chapter02.testShoppingCartCookies(conn);
//        chapter02.testCacheRequest(conn);
        chapter02.testCacheRows(conn);


    }

    private void testCacheRows(Jedis conn) {

        String itemId = "商品X";

        scheduleRowCache(conn, itemId, 5);
//        获取所有数据
        Set<Tuple> schedules = conn.zrangeWithScores(z_schedule_key, 0, -1);
        Set<Tuple> delays = conn.zrangeWithScores(z_delay_key, 0, -1);
        for (Tuple tuple : schedules) {
            System.out.println("  " + tuple.getElement() + ", " + tuple.getScore());

        }

        for (Tuple tuple : delays) {
            System.out.println(" 延时  " + tuple.getElement() + ", " + tuple.getScore());
        }

        CacheRowsThread thread = new CacheRowsThread();
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String invCache = conn.get(s_inv_key_prefix + itemId);
        System.out.println("缓存的库存和商品信息:\n"+invCache);

        System.out.println("We'll check again in 5 seconds...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Notice that the data has changed...");

        String invCache1 = conn.get(s_inv_key_prefix + itemId);
        System.out.println("缓存的库存和商品信息:\n"+invCache1);

        thread.quit();
    }

    private void scheduleRowCache(Jedis conn, String itemId, int delay) {

        Long delayValue = conn.zadd(z_delay_key, delay, itemId);
        Long scheduleValue = conn.zadd(z_schedule_key, System.currentTimeMillis() / 1000, itemId);

        System.out.println("    "+delayValue+" , "+scheduleValue);
    }



    private void testCacheRequest(Jedis conn) {

        String token = UUID.randomUUID().toString();
        String currentUser = "刘万祥";
        String itemId = "商品X";

        updateToken(conn,token,currentUser,itemId);

        Callback    callback= requestUrl -> {
//                这里要通过网络请求web 服务器 获取页面内容了
            return "return content";
        };

        String requestUrl = "http://www.shopping.com/p/123";
        String result1 = cacheRequest(conn, requestUrl, callback);
        String result2 = cacheRequest(conn, requestUrl, null);
        System.out.println(result1.equals(result2));

    }


    private String  cacheRequest(Jedis conn, String requestUrl, Callback callback) {

        String content;

        if (canCache(conn, requestUrl)) {

            String pageKey = z_cache_key_prefix + hashRequest(requestUrl);
            content = conn.get(pageKey);

            if (content == null && callback != null) {
                content = callback.call(requestUrl);
//                直接缓存整个页面内容,不单独放一张表了
                conn.setex(pageKey, 5 * 60, content);
            }
        } else {
            content = callback == null ? null : callback.call(requestUrl);

        }


        return content;

    }

    private String hashRequest(String requestUrl) {

        return requestUrl.hashCode() + "";
    }

    /**
     * 判断是否能缓存的依据是,只缓存分值在前面1000的,分值小的不存
     * @param conn
     * @param requestUrl
     * @return
     */
    private boolean canCache(Jedis conn, String requestUrl) {
//        这里应该是获取商品Id的,这里简单模拟一下
        String itemId=requestUrl;

        Long rank = conn.zrank(z_viewed_key_prefix, itemId);

        

        return rank!=null && rank< 1000;
    }


    private void testShoppingCartCookies(Jedis conn) {
        String token = UUID.randomUUID().toString();
        String currentUser = "刘万祥";
        String itemId = "商品X";
        updateToken(conn,token,currentUser,itemId);

        addToCart(conn, token, itemId, 3);
//        获取该用户的购物车
        Map<String, String> cartEntrys = conn.hgetAll(z_cart_key_prefix + token);
        System.out.println("购物车商品数目" + cartEntrys.size());


        CleanFullSessionsThread thread = new CleanFullSessionsThread(10);
        thread.start();
        try {
            Thread.sleep(1000);
            thread.quit();
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cartEntrys = conn.hgetAll(z_cart_key_prefix + token);
        System.out.println("现在的购物车商品数目" + cartEntrys.size());


    }

    private void addToCart(Jedis conn, String token, String itemId, long count) {
        if (count <= 0) {
            conn.hdel(z_cart_key_prefix + token, itemId);

        } else {
            conn.hset(z_cart_key_prefix + token, itemId, count+"");
        }
    }

    private void testCheckCookies(Jedis conn) {

        String token = UUID.randomUUID().toString();
        String currentUser = "刘万祥";
        String itemId = "商品X";
        updateToken(conn, token, currentUser, itemId);

        //        检查用户登录token
        checkToken(conn, token);

        CleanSessionsThread thread = new CleanSessionsThread(20);
        thread.start();
        try {
            Thread.sleep(1000);
            thread.quit();
            Thread.sleep(2000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long logins = conn.hlen(h_login_key);
        System.out.println("还剩下这么多人登录:" + logins);




    }

    private  void checkToken(Jedis conn, String token) {
        String tokenValue= conn.hget(h_login_key, token);
    }

    private  void updateToken(Jedis conn, String token,String currentUser,String itemId) {
        long now = System.currentTimeMillis() / 1000;
        conn.hset(h_login_key, token, currentUser);
//        最近登录表,这里涉及到时间的,弄个有序集合
        conn.zadd(z_recent_key, now, token);

//        用户最近浏览商品
        if (!Objects.isNull(itemId)) {
            conn.zadd(z_viewed_key_prefix+token, now, itemId);
            long viewdSize = conn.zcard(z_viewed_key_prefix + token);
            if (viewdSize > 100) {
                //            删除后25个元素
                conn.zremrangeByRank(z_viewed_key_prefix+token, viewdSize-26, viewdSize-1);
            }

//           每浏览一次分值减少1.浏览最多的分值最低,排名靠前.
            conn.zincrby(z_viewed_key_prefix, -1, itemId);

        }
    }

    private   class CleanSessionsThread extends Thread{
        private final Jedis   conn;
        private boolean quit;
        private final long    limit;

        public CleanSessionsThread(long limit) {
            this.conn = new Jedis("localhost");
            conn.select(15);
            this.limit = limit;
        }

        public void quit() {
            this.quit = true;
        }

        @Override
        public void run() {

            while (!quit) {
                long size = conn.zcard(z_recent_key);
//                如果长度没有达到上限,休息1秒在执行
                if (size < limit) {

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    continue;
                }

                long index = Math.min(size - limit, 100);
//                redis 是包头包尾的,如果要删除100个的话, 是 0,99 这样
                Set<String> recents = conn.zrange(z_recent_key, 0, index - 1);
//                recent 表里的 member存的就是 token.
                String[] removeArrays = recents.toArray(new String[recents.size()]);


                conn.hdel(h_login_key, removeArrays);
                conn.zrem(z_recent_key, removeArrays);
//                viewd表的键 为 viewd:token 形式的,删除的时候,因为是删除这个表,是根据键 名去删除的.这里需要组装一下

                List<String> viewdKeys = recents.stream().map(token -> z_viewed_key_prefix + token).collect(Collectors.toList());

                String[] viewdArrays = viewdKeys.toArray(new String[viewdKeys.size()]);
                conn.del(viewdArrays);

            }
        }
    }


    private class CleanFullSessionsThread extends Thread {
        private final Jedis conn;
        private boolean quit;
        private final long limit;

        public CleanFullSessionsThread(long limit) {
            this.conn = new Jedis("localhost");
            conn.select(15);
            this.limit = limit;
        }

        public void quit() {
            this.quit = true;
        }

        @Override
        public void run() {

            while (!quit) {

                long size = conn.zcard(z_recent_key);

                if (size < limit) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    continue;
                }

//                删除前100个用户信息
                long endIndex = Math.min(size - limit, 100);
                Set<String> recents = conn.zrange(z_recent_key, 0, endIndex - 1);

                String[] tokens = recents.toArray(new String[recents.size()]);

                conn.hdel(h_login_key, tokens);
                conn.zrem(z_recent_key, tokens);
//              这种写法是分开来表示每个集合的
//                List<String> viewdList = new ArrayList<>();
//                List<String> cartList = new ArrayList<>();
//                for (String token : recents) {
//                    viewdList.add(z_viewed_key_prefix + token);
//                    cartList.add(z_cart_key_prefix + token);
//                }
//
//                conn.del(viewdList.toArray(new String[viewdList.size()]));
//                conn.del(cartList.toArray(new String[cartList.size()]));

//               viewd表和cart表都 放在同一个 sessionKeys里面
                List<String> sessionKeys = new ArrayList<>();
                for (String token : recents) {
                    sessionKeys.add(z_viewed_key_prefix + token);
                    sessionKeys.add(z_cart_key_prefix + token);
                }

                conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
            }

        }
    }

    private interface Callback {
        String call(String requestUrl);
    }

    private class CacheRowsThread extends Thread {
        private Jedis conn;
        private boolean quit;

        public CacheRowsThread() {
            this.conn = new Jedis("localhost");
            conn.select(15);
        }

        public void quit() {
            this.quit = true;
        }

        @Override
        public void run() {

            while (!quit) {
                Set<Tuple> tuples = conn.zrangeWithScores(z_schedule_key, 0, 0);
                Tuple firstEle = tuples.size() > 0 ? tuples.iterator().next() : null;
                long now = System.currentTimeMillis() / 1000;
                if (firstEle == null || firstEle.getScore() > now) {

                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    continue;
                }

                String rowId = firstEle.getElement();
                double delayTime = conn.zscore(z_delay_key, rowId);
                if (delayTime <= 0) {
                    conn.zrem(z_schedule_key, rowId);
                    conn.zrem(z_delay_key, rowId);
                    conn.del(s_inv_key_prefix + rowId);
                    continue;
                }

                Inventory inv = new Inventory(rowId);
                conn.zadd(z_schedule_key, now + delayTime, rowId);
                Gson gson = new Gson();
                conn.set(s_inv_key_prefix + rowId, gson.toJson(inv));
            }

        }
    }


    private class Inventory {
        private String id;
        private String data;
        private long time;

        private Inventory (String id) {
            this.id = id;
            this.data = "data to cache...";
            this.time = System.currentTimeMillis() / 1000;
        }

        public  Inventory get(String id) {
//            这里应该是去数据库获取,或者调用接口获取数据了
            return new Inventory(id);
        }
    }

    private class RescaleViewedThread extends Thread {
        private Jedis conn;
        private boolean quit;

        public RescaleViewedThread() {
            this.conn = new Jedis("localhost");
            conn.select(15);
        }

        public void quit() {
            this.quit = true;
        }

        @Override
        public void run() {

            while (!quit) {
                long viewedSize = conn.zcard(z_viewed_key_prefix);
                if (viewedSize > 100) {
//                    移除最后20个
                    conn.zremrangeByRank(z_viewed_key_prefix, viewedSize - 21, viewedSize - 1);
//                    conn.zinterstore()
                    try {
//                        睡5分钟在检查
                        Thread.sleep(1000 * 60 * 5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

}










